package org.covalence.core.visitor;

import org.apache.log4j.Logger;
import org.covalence.core.Constants;
import org.covalence.core.util.CTable;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
 * Creates the bytecode to intercept methods which set the value of a collection
 * property on a bound class.  The enhanced set method will check if the new
 * collection is already an Observable type.  If it's not it will wrap the collection
 * inside an Observable collection.  If the collection is already nested inside
 * the containing object it will simply set the value and fire the property 
 * change event.  If it's not nested whith the containing object the set method
 * will add the Collection as a nested observable and set its parent to the 
 * containing object.  If the original code looks like:
 * <pre>
 * public void setList(List list) {
 *   this.list = list;
 * }
 * </pre>
 * then the enhanced code will look like:
 * <pre>
 * public void setList(List list) {
 * 
 *   // this is created by SetColMethodVisitor
 *   if (!(list instanceof Observable)) {
 *     list = new CList(list);
 *   }
 *   if (((Observable) list).getParent() != this) {
 *     ((Observable) this).addNestedObservable((Observable) list, "list");
 *     ((Observable) list).setParent(this);
 *   }
 *   
 *   // the below is created by SetMethodVisitor
 *   List oldValue = this.list;
 *   List newValue = list;
 *   this.list = list;
 *   _pcs.firePropertyChange("list", oldValue, newValue);
 * }
 * </pre> 
 * @author tslusser
 */
public class SetCollectionMethodVisitor extends MethodAdapter 
		implements Constants, Opcodes {

	private String desc;

	private String property;

	private static Logger log = Logger.getLogger(SetCollectionMethodVisitor.class);

	public SetCollectionMethodVisitor(MethodVisitor mv, String property, 
			String desc) {
		super(mv);
		this.property = property;
		this.desc = desc;
		log.trace("[property:" + property + ", desc:" + desc + "]");
	}

	public void visitCode() {
		log.trace("[]");

		// if (!(collection instanceof Observable)) {
		//     collection = new CCollection(collection);
		// }
		Label l0 = new Label();
		mv.visitLabel(l0);
		mv.visitVarInsn(ALOAD, 1);
		mv.visitTypeInsn(INSTANCEOF, OBSERVABLE);
		Label l1 = new Label();
		mv.visitJumpInsn(IFNE, l1);
		Label l2 = new Label();
		mv.visitLabel(l2);
		mv.visitTypeInsn(NEW, CTable.translate(desc));
		mv.visitInsn(DUP);
		mv.visitVarInsn(ALOAD, 1);
		mv.visitMethodInsn(INVOKESPECIAL, CTable.translate(desc), "<init>", 
				"(Ljava/lang/Object;)V");
		mv.visitVarInsn(ASTORE, 1);
		mv.visitLabel(l1);
		
		// if (((Observable) collection).getParent() != this) {
		//     ((Observable) this).addNestedObservable((Observable) collection, "collection");
		//     ((Observable) collection).setParent(this);
		// }
		mv.visitVarInsn(ALOAD, 1);
		mv.visitTypeInsn(CHECKCAST, OBSERVABLE);
		mv.visitMethodInsn(INVOKEINTERFACE, OBSERVABLE, "getParent", 
				"()Ljava/lang/Object;");
		mv.visitVarInsn(ALOAD, 0);
		Label l3 = new Label();
		mv.visitJumpInsn(IF_ACMPEQ, l3);
		Label l4 = new Label();
		mv.visitLabel(l4);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitTypeInsn(CHECKCAST, OBSERVABLE);
		mv.visitVarInsn(ALOAD, 1);
		mv.visitTypeInsn(CHECKCAST, OBSERVABLE);
		mv.visitLdcInsn(property);
		mv.visitMethodInsn(INVOKEINTERFACE, OBSERVABLE, "addNestedObservable", 
				ADD_NESTED_OBSERVABLE_SIG);
		mv.visitVarInsn(ALOAD, 1);
		mv.visitTypeInsn(CHECKCAST, OBSERVABLE);
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKEINTERFACE, OBSERVABLE, "setParent", 
				SET_PARENT_SIG);
		mv.visitLabel(l3);
		
		super.visitCode();
	}
}
